﻿using System;

namespace CommonFramework.FixMath
{
    [Serializable]
    public struct FVector3 : IEquatable<FVector3>
    {
        private int _rawX;
        private int _rawY;
        private int _rawZ;

        public FFloat X
        {
            get => FFloat.FromRawInt(_rawX);
            set => _rawX = value.RawVal();
        }
        public int RawX => _rawX;
        public FFloat Y
        {
            get => FFloat.FromRawInt(_rawY);
            set => _rawY = value.RawVal();
        }
        public int RawY => _rawY;
        public FFloat Z
        {
            get => FFloat.FromRawInt(_rawZ);
            set => _rawZ = value.RawVal();
        }
        public int RawZ => _rawZ;

        public static readonly FVector3 Zero = FVector3.FromRawInt( 0, 0, 0);
        public static readonly FVector3 One = FVector3.FromRawInt( FFloat.Precision, FFloat.Precision, FFloat.Precision);
        public static readonly FVector3 Half = FVector3.FromRawInt(FFloat.Precision / 2, FFloat.Precision / 2, FFloat.Precision / 2);

        public static readonly FVector3 Forward = FVector3.FromRawInt(0, 0, FFloat.Precision);
        public static readonly FVector3 Up = FVector3.FromRawInt(0, FFloat.Precision, 0);
        public static readonly FVector3 Right = FVector3.FromRawInt(FFloat.Precision, 0, 0);
        public static readonly FVector3 Back = FVector3.FromRawInt(0, 0, -FFloat.Precision);
        public static readonly FVector3 Down = FVector3.FromRawInt(0, -FFloat.Precision, 0);
        public static readonly FVector3 Left = FVector3.FromRawInt(-FFloat.Precision, 0, 0);

        private FVector3(int rawX, int rawY, int rawZ)
        {
            this._rawX = rawX;
            this._rawY = rawY;
            this._rawZ = rawZ;
        }

        public static FVector3 FromRawInt(int rawX, int rawY, int rawZ)
        {
            return new FVector3(rawX, rawY, rawZ);
        }
        public static FVector3 FromInt(int x, int y, int z)
        {
            return new FVector3(x * FFloat.Precision, y * FFloat.Precision, z * FFloat.Precision);
        }
        public static FVector3 FromRawLong(long rawX, long rawY, long rawZ)
        {
            return new FVector3((int)rawX,(int)rawY, (int)rawZ);
        }
        public static FVector3 FromLong(long x, long y, long z)
        {
            return new FVector3((int) x * FFloat.Precision, (int) y * FFloat.Precision, (int) z * FFloat.Precision);
        }
        public static FVector3 FromFFloat(FFloat x, FFloat y, FFloat z)
        {
            return FromRawInt(x.RawVal(), y.RawVal(), z.RawVal());
        }


        public static implicit operator FVector2(FVector3 v)
        {
            return FVector2.FromRawInt(v._rawX, v._rawY);
        }

        public FFloat Magnitude
        {
            get
            {
                long x = (long)this._rawX;
                long y = (long)this._rawY;
                long z = (long)this._rawZ;
                return FFloat.FromRawLong(FMathUtil.Sqrt(x * x + y * y + z * z));
            }
        }
        public FFloat sqrMagnitude
        {
            get
            {
                long x = (long)this._rawX;
                long y = (long)this._rawY;
                long z = (long)this._rawZ;
                return FFloat.FromRawLong((x * x + y * y + z * z) / FFloat.Precision);
            }
        }

        public FVector3 abs =>  FVector3.FromRawInt( FMathUtil.Abs(this._rawX), FMathUtil.Abs(this._rawY), FMathUtil.Abs(this._rawZ));

        public FVector3 Normalize()
        {
            return Normalize((FFloat)1);
        }
        public FVector3 Normalize(FFloat newMagn)
        {
            long num = (long)(this._rawX * 100);
            long num2 = (long)(this._rawY * 100);
            long num3 = (long)(this._rawZ * 100);
            long num4 = num * num + num2 * num2 + num3 * num3;
            if (num4 == 0L)
            {
                return this;
            }

            long b = (long)FMathUtil.Sqrt(num4);
            long num5 = newMagn.RawVal();
            this._rawX = (int)(num * num5 / b);
            this._rawY = (int)(num2 * num5 / b);
            this._rawZ = (int)(num3 * num5 / b);
            return this;
        }
        public FVector3 Normalized
        {
            get
            {
                long num = (long)((long)this._rawX << 7);
                long num2 = (long)((long)this._rawY << 7);
                long num3 = (long)((long)this._rawZ << 7);
                long num4 = num * num + num2 * num2 + num3 * num3;
                if (num4 == 0L)
                {
                    return FVector3.Zero;
                }

                var ret = new FVector3();
                long b = (long)FMathUtil.Sqrt(num4);
                long num5 = FFloat.Precision;
                ret._rawX = (int)(num * num5 / b);
                ret._rawY = (int)(num2 * num5 / b);
                ret._rawZ = (int)(num3 * num5 / b);
                return ret;
            }
        }

        public FVector3 RotateY(FFloat degree)
        {
            FFloat s;
            FFloat c;
            FMathUtil.SinCos(out s, out c, FFloat.FromRawLong( degree.RawVal() * 31416L / 1800000L));
            FVector3 vInt;
            vInt._rawX = (int)(((long)this._rawX * s.RawVal() + (long)this._rawZ * c.RawVal()) / FFloat.Precision);
            vInt._rawZ = (int)(((long)this._rawX * -c.RawVal() + (long)this._rawZ * s.RawVal()) / FFloat.Precision);
            vInt._rawY = 0;
            return vInt.Normalized;
        }

        public static bool operator ==(FVector3 lhs, FVector3 rhs)
        {
            return lhs._rawX == rhs._rawX && lhs._rawY == rhs._rawY && lhs._rawZ == rhs._rawZ;
        }
        public static bool operator !=(FVector3 lhs, FVector3 rhs)
        {
            return lhs._rawX != rhs._rawX || lhs._rawY != rhs._rawY || lhs._rawZ != rhs._rawZ;
        }
        public static FVector3 operator -(FVector3 lhs, FVector3 rhs)
        {
            lhs._rawX -= rhs._rawX;
            lhs._rawY -= rhs._rawY;
            lhs._rawZ -= rhs._rawZ;
            return lhs;
        }
        public static FVector3 operator -(FVector3 lhs)
        {
            lhs._rawX = -lhs._rawX;
            lhs._rawY = -lhs._rawY;
            lhs._rawZ = -lhs._rawZ;
            return lhs;
        }
        public static FVector3 operator +(FVector3 lhs, FVector3 rhs)
        {
            lhs._rawX += rhs._rawX;
            lhs._rawY += rhs._rawY;
            lhs._rawZ += rhs._rawZ;
            return lhs;
        }
        public static FVector3 operator *(FVector3 lhs, FVector3 rhs)
        {
            lhs._rawX = (int)(((long)(lhs._rawX * rhs._rawX)) / FFloat.Precision);
            lhs._rawY = (int)(((long)(lhs._rawY * rhs._rawY)) / FFloat.Precision);
            lhs._rawZ = (int)(((long)(lhs._rawZ * rhs._rawZ)) / FFloat.Precision);
            return lhs;
        }
        public static FVector3 operator *(FVector3 lhs, FFloat rhs)
        {
            lhs._rawX = (int)(((long)(lhs._rawX * rhs.RawVal())) / FFloat.Precision);
            lhs._rawY = (int)(((long)(lhs._rawY * rhs.RawVal())) / FFloat.Precision);
            lhs._rawZ = (int)(((long)(lhs._rawZ * rhs.RawVal())) / FFloat.Precision);
            return lhs;
        }
        public static FVector3 operator /(FVector3 lhs, FFloat rhs)
        {
            lhs._rawX = (int)(((long)lhs._rawX * FFloat.Precision) / rhs.RawVal());
            lhs._rawY = (int)(((long)lhs._rawY * FFloat.Precision) / rhs.RawVal());
            lhs._rawZ = (int)(((long)lhs._rawZ * FFloat.Precision) / rhs.RawVal());
            return lhs;
        }
        public static FVector3 operator *(FFloat rhs, FVector3 lhs)
        {
            lhs._rawX = (int)(((long)(lhs._rawX * rhs.RawVal())) / FFloat.Precision);
            lhs._rawY = (int)(((long)(lhs._rawY * rhs.RawVal())) / FFloat.Precision);
            lhs._rawZ = (int)(((long)(lhs._rawZ * rhs.RawVal())) / FFloat.Precision);
            return lhs;
        }

        public override string ToString()
        {
            return string.Format("({0},{1},{2})", 
                _rawX * FFloat.PrecisionInverse, 
                _rawY * FFloat.PrecisionInverse,
                _rawZ * FFloat.PrecisionInverse);
        }
        public override bool Equals(object o)
        {
            if (o == null)
            {
                return false;
            }
            FVector3 other = (FVector3)o;
            return this._rawX == other._rawX && this._rawY == other._rawY && this._rawZ == other._rawZ;
        }
        public bool Equals(FVector3 other)
        {
            return this._rawX == other._rawX && this._rawY == other._rawY && this._rawZ == other._rawZ;
        }
        public override int GetHashCode()
        {
            return this._rawX * 73856093 ^ this._rawY * 19349663 ^ this._rawZ * 83492791;
        }

        public FFloat this[int index]
        {
            get
            {
                switch (index)
                {
                    case 0: return X;
                    case 1: return Y;
                    case 2: return Z;
                    default: throw new IndexOutOfRangeException("vector idx invalid" + index);
                }
            }
            set
            {
                switch (index)
                {
                    case 0: _rawX = value.RawVal(); break;
                    case 1: _rawY = value.RawVal(); break;
                    case 2: _rawZ = value.RawVal(); break;
                    default: throw new IndexOutOfRangeException("vector idx invalid" + index);
                }
            }
        }

        public static FFloat Dot(ref FVector3 lhs, ref FVector3 rhs)
        {
            var val = ((long)lhs._rawX) * rhs._rawX + ((long)lhs._rawY) * rhs._rawY + ((long)lhs._rawZ) * rhs._rawZ;
            return FFloat.FromRawLong(val / FFloat.Precision);
        }
        public static FFloat Dot(FVector3 lhs, FVector3 rhs)
        {
            var val = ((long)lhs._rawX) * rhs._rawX + ((long)lhs._rawY) * rhs._rawY + ((long)lhs._rawZ) * rhs._rawZ;
            return FFloat.FromRawLong(val / FFloat.Precision);
        }
        public static FVector3 Cross(ref FVector3 lhs, ref FVector3 rhs)
        {
            return FVector3.FromRawLong
            (
                ((long)lhs._rawY * rhs._rawZ - (long)lhs._rawZ * rhs._rawY) / FFloat.Precision,
                ((long)lhs._rawZ * rhs._rawX - (long)lhs._rawX * rhs._rawZ) / FFloat.Precision,
                ((long)lhs._rawX * rhs._rawY - (long)lhs._rawY * rhs._rawX) / FFloat.Precision
            );
        }
        public static FVector3 Cross(FVector3 lhs, FVector3 rhs)
        {
            return FVector3.FromRawLong
            (
                ((long)lhs._rawY * rhs._rawZ - (long)lhs._rawZ * rhs._rawY) / FFloat.Precision,
                ((long)lhs._rawZ * rhs._rawX - (long)lhs._rawX * rhs._rawZ) / FFloat.Precision,
                ((long)lhs._rawX * rhs._rawY - (long)lhs._rawY * rhs._rawX) / FFloat.Precision
            );
        }

        public static FVector3 Lerp(FVector3 a, FVector3 b, FFloat f)
        {
            return FVector3.FromRawLong
            (
                (int)(((long)(b._rawX - a._rawX) * f.RawVal()) / FFloat.Precision) + a._rawX,
                (int)(((long)(b._rawY - a._rawY) * f.RawVal()) / FFloat.Precision) + a._rawY,
                (int)(((long)(b._rawZ - a._rawZ) * f.RawVal()) / FFloat.Precision) + a._rawZ
                );
        }
    }
}